import { CreateProgram } from "../Utils.js";

const defAttr = () => ({
  gl: null,
  children: [],
  programs: new Map(),
  drawObjs: new Map(),
});

//世界对象
export default class Scene {
  constructor(attr) {
    Object.assign(this, defAttr(), attr);

    this.drawId = 0; //渲染ID/ 用来检索

    this.textureDepth = this.gl.createTexture();
    this.frameBuffer = this.gl.createFramebuffer();
    this.depthBuffer = this.gl.createRenderbuffer();
    this.texture = this.gl.createTexture();
  }

  init() {
    const { gl, children } = this;
    children.forEach((element) => {
      element.init(gl);
    });
  }

  regProgram(programName, { program, attribs, uniforms }) {
    const { programs, gl } = this;
    if (typeof programs.get(programName) != "undefined") {
      console.log(`[${programName}]以注册`);
      return;
    }

    let attribNames = new Map();
    let uniformNames = new Map();

    gl.useProgram(program);

    for (let att of attribs) {
      attribNames.set(att, gl.getAttribLocation(program, att));
    }
    for (let uni of uniforms) {
      uniformNames.set(uni, gl.getUniformLocation(program, uni));
    }
    programs.set(programName, {
      program: program,
      attribs: attribNames,
      uniforms: uniformNames,
    });
  }

  _getChildren(obj3D) {
    let arr = [];
    const child = obj3D.children;
    if (child.length > 0) {
      for (let obj of child) {
        for (let i of this._getChildren(obj)) {
          arr.push(i);
        }
      }
    }
    for (let i of child) {
      arr.unshift(i); //头插
    }
    return arr;
  }

  add(...elements) {
    const { gl, children, drawObjs } = this;
    elements.forEach((u) => {
      children.push(u);
      u.parent = this;

      u.init(gl);
      this.addDrawObjs(u, drawObjs);

      //递归出所有子类
      let child = this._getChildren(u);
      for (let obj of child) {
        this.addDrawObjs(obj, drawObjs);
      }
    });
  }
  addRange(array) {
    const { gl, children, drawObjs } = this;
    for (let i = 0; i < array.length; i++) {
      children.push(array[i]);
      array[i].parent = this;

      array[i].init(gl);
      this.addDrawObjs(array[i], drawObjs);

      //递归出所有子类
      let child = this._getChildren(array[i]);
      for (let obj of child) {
        this.addDrawObjs(obj, drawObjs);
      }
    }
  }
  //如果父类对象已经加入scene中后再向父类对象添加元素, 则需要手动调用一次
  addDrawObjs(element, drawObjsMap) {
    if (element.mat == null) return;

    const key = element.mat.program;
    //查询key是否存在
    if (typeof drawObjsMap.get(key) == "undefined") {
      //插入新的
      drawObjsMap.set(key, [element]);
    } else {
      //添加上
      drawObjsMap.get(key).push(element);
    }
    element.drawId = this.drawId;
    this.drawId++;
  }

  remove(element) {
    const { children, drawObjs } = this;
    const i = children.indexOf(element);
    if (i != -1) {
      this.removeDrawObjs(element, drawObjs);
      children.splice(i, 1);

      //递归出所有子类
      let child = this._getChildren(element);
      for (let obj of child) {
        this.removeDrawObjs(obj, drawObjs);
      }
    }
  }
  //如果父类对象已经加入scene中后再向父类对象添加元素, 则需要手动调用一次
  removeDrawObjs(element, drawObjsMap) {
    if (element.mat == null) return;
    const key = element.mat.program;
    const index = drawObjsMap.get(key).indexOf(element);
    drawObjsMap.get(key).splice(index, 1);
  }

  clear() {
    this.children = [];
    this.drawObjs = new Map();
  }

  setUniform(name, uniform) {
    const { children } = this;
    children.forEach((u) => {
      u.setUniform(name, uniform);
    });
  }
  setAttrib(name, attrib) {
    const { children } = this;
    children.forEach((u) => {
      u.setAttrib(name, attrib);
    });
  }
  setMap(name, map) {
    const { children } = this;
    children.forEach((u) => {
      u.setMap(name, map);
    });
  }

  draw([r, g, b, a] = [0, 0, 0, 1]) {
    const { gl, programs, drawObjs } = this;

    gl.clearColor(r, g, b, a);
    gl.clear(gl.COLOR_BUFFER_BIT);

    for (let [key, value] of drawObjs) {
      const { program, attribs, uniforms } = programs.get(key);
      gl.useProgram(program);

      value.forEach((element) => {
        element.update(gl, attribs, uniforms);
        element.draw(gl);
      });
    }
  }

  getLocation(programName, locationName) {
    const { programs } = this;
    let data = programs.get(programName);
    var name = locationName.split("_")[0];
    if (name == "a") {
      return data.attribs.get(locationName);
    } else if (name == "u") {
      return data.uniforms.get(locationName);
    } else {
      return null;
    }
  }

  allTransform() {
    const { children } = this;
    for (let obj of children) {
      obj.allTransform();
    }
  }

  addAxisProgram() {
    const { gl } = this;
    const axisVS = `    attribute vec4 a_Position;
        uniform mat4 u_ModelM4;
        uniform mat4 u_PvM4;
        varying vec3 v_Position;
        void main(){
            gl_Position = u_PvM4 * u_ModelM4 * a_Position;
            v_Position = vec3(a_Position);
        } `;
    const axisFS = `precision mediump float;
        varying vec3 v_Position;
        uniform vec4 u_Color;
        void main(){
            vec4 color = u_Color;
            if(v_Position.x > 0.0 && v_Position.x < 1.0){
                color = vec4(1.0, 0.0, 0.0, 1.0);
            }
            if(v_Position.y > 0.0 && v_Position.y < 1.0){
                color = vec4(0.0, 1.0, 0.0, 1.0);
            }
            if(v_Position.z > 0.0 && v_Position.z < 1.0){
                color = vec4(0.0, 0.0, 1.0, 1.0);
            }
            gl_FragColor = color;
        }`;
    this.regProgram("axis", {
      program: CreateProgram(gl, axisVS, axisFS),
      attribs: ["a_Position"],
      uniforms: ["u_PvM4", "u_ModelM4", "u_Color"],
    });
    return "axis";
  }
}
